import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * Class Mono representa el dibujo que controla el jugador.
 * 
 * @author (Programacion Orientada A Objetos - Gonzalez Donat - Neri Andriano) 
 * @version (15/05/12)
 */
public class Mono extends Actor
{
    private GreenfootImage[] imagenesd;                 //arreglo de imagenes para el movimiento a la derecha del Mono
    private GreenfootImage[] imagenesi;                 //arreglo de imagenes para el movimiento a la izquierda del Mono
    private GreenfootImage[] imagenesbd;                //arreglo de imagenes para el brinco a la derecha del Mono
    private GreenfootImage[] imagenesbi;                //arreglo de imagenes para el brinco a la izquierda del Mono
    private GreenfootImage[] imagenesba;                //arreglo de imagenes para el brinco hacia arriba del Mono
    private GreenfootImage[] imagenescae;               //arreglo de imagenes para la caida del Mono    
    private GreenfootImage[] imagenessub;               //arreglo de imagenes para el movimiento de subir del Mono
    private static final int CAMINA = 20;       //constante que indica el numero de imagenes de caminar a cargar por cada direccion
    private static final int BRINCA = 16;       //constante que indica el numero de imagenes de brinco a cargar por cada direccion
    private static final int CAE = 9;           //constante que indica el numero de imagenes del caer del jugador  
    private static final int SUBE = 9;          //constante que indica el numero de imagenes de subir del jugador  
    public static final int DER = 3;            //constante que indica direccion a la derecha del jugador
    public static final int IZQ = 9;            //constante que indica direccion a la izquierda del jugador
    public static final int ARR = 12;           //constante que indica direccion hacia arriba del jugador
    public static final int ABA = 6;            //constante que indica direccion hacia abajo del jugador
    public static final int NODIR = 0;          //constante que indica sin direccion del jugador
    private static final boolean V = true;      //equivalente a true pero mas corto
    private static final boolean F = false;     //equivalente a false pero mas corte
    private int i;                              //indice de la imagen del Mono
    private int dir;                            //direccion hacia la que esta volteado el mono
    private boolean brincando;                  //indica si el Mono se encuentra brincando
    private boolean caminando;                  //indica si el Mono se encuentra caminando
    private boolean frenando;                   //indica si el Mono se encuentra frenando
    private boolean subiendo;                   //indica si el Mono se encuentra subiendo
    private boolean cayendo;                    //indica si el Mono se encuentra cayendo
    private int x;                              //posicion en x del Mono
    private int y;                              //posicion en y del Mono
    private Piso piso;                          //Hace referencia al piso con el que hace contacto el Mono
    private Escalera esca;                      //Hace referencia a la escalera con el que hace contacto el Mono
    private Obstaculo obs;                      //Hace referencia al obstaculo con el que hace contacto el Mono
    private int vidas;                          //numero de vidas restantes
    private GoP pierde;                         //
    private GreenfootSound salto;               //Variable para el sonido del salto
    private GreenfootSound muere;               //Variable para el sonido de cuando muere el mono
    /**
     * Constructor para la clase Mono
     */
    public Mono()
    {
        pierde = new GoP(false);
        imagenesd = new GreenfootImage[CAMINA];
        imagenesi = new GreenfootImage[CAMINA];
        imagenesbd = new GreenfootImage[BRINCA];
        imagenesbi = new GreenfootImage[BRINCA];
        imagenesba = new GreenfootImage[BRINCA];
        imagenescae = new GreenfootImage[CAE];
        imagenessub = new GreenfootImage[SUBE];
        for(i = 0;i < CAMINA; i++)
        {
            imagenesd[i] = new GreenfootImage("mono1."+(i+1)+".png");
            imagenesi[i] = new GreenfootImage("mono2."+(i+1)+".png");
            if(i < BRINCA)
            {
                imagenesbd[i] = new GreenfootImage("mono3."+(i+1)+".png");
                imagenesbi[i] = new GreenfootImage("mono4."+(i+1)+".png");
                imagenesba[i] = new GreenfootImage("mono5."+(i+1)+".png");
            }
            imagenesi[i] = new GreenfootImage("mono2."+(i+1)+".png");
            if(i < CAE)
                imagenescae[i] = new GreenfootImage("mono6."+(i+1)+".png");
            if(i < SUBE)
                imagenessub[i] = new GreenfootImage("mono7."+(i+1)+".png");
        }
        i = 0;
        dir = NODIR;
        piso = null;
        esca = null;
        obs = null;
        vidas = 3;
        this.accionActiva(F,F,F,F,F);
        salto = new GreenfootSound("salto.wav");
        muere = new GreenfootSound("muere.wav");
    }
    /**
     * Se encarga de ejecutar las acciones necesarias
     * para que el Mono actue en el Escenario
     */
    public void act() 
    {
        x = this.getX();
        y = this.getY();
        this.asignaEscenario();
        this.activaAlgo();
        this.desactivaAlgo();
        this.actua();
        this.checaObstaculo();
    }
    /**
     * Verifica la tecla presionada y activa la accion
     * que le corresponde
     */
     public void activaAlgo()
    {
        if(Greenfoot.isKeyDown("R"))
        {
           ((Escenario)this.getWorld()).cambiaEscenario(3);
        }
        
        if(Greenfoot.isKeyDown("space"))
            activaBrinco();
        if(Greenfoot.isKeyDown("right")){
            if(brincando && (dir==ARR || dir == NODIR || dir == ABA) && esca==null)
                dir = dir;
            else
                dir = DER;
            activaCaminar();
        }
        if(Greenfoot.isKeyDown("left")){
            if(brincando && (dir==ARR || dir == NODIR || dir == ABA) && esca==null)
                dir = dir;
            else
                dir = IZQ;
            activaCaminar();
        }
        if(Greenfoot.isKeyDown("up")){
            if(dir==DER || dir == IZQ)
                dir = dir;
            else
                dir = ARR;
            activaSubir();
        }
        if(Greenfoot.isKeyDown("down")){
            if(dir==DER || dir == IZQ)
                dir = dir;
            else
                dir = ABA;
            activaSubir();
        }
        activaCaer();
        activaFrenar();
    }
    /**
     * Verifica que la activacion de alguna accion
     * sea valida y si no lo es la desactiva
     */
    public void desactivaAlgo()
    {
        if(brincando && i>4 && piso != null){
            i=1;
            brincando = false;
        }  
        if( /* techo!= null || */(caminando && piso == null && esca == null))
            caminando = false;
        if(/* techo!= null || */(frenando  && (i==0 || piso == null)))
            frenando = false;
        if(/* techo!= null || */ (subiendo && esca == null)){
            i=1;
            subiendo = false;
        }
        if(cayendo && piso != null){
            i=1;
            cayendo = false;
        }
        if(!brincando && !caminando && !frenando && !subiendo && !cayendo){
            dir = NODIR;
            this.setImage(imagenesd[i=0]);
        }
        
    }
    /**
     * hace el movimiento que le corresponde al mono
     * dependiendo de la accion activa
     */
    public void actua()
    {
        if(brincando)
            this.brincar();
        if(caminando)
            this.caminar();
        if(frenando)
            this.frenar();
        if(subiendo)
            this.subir();
        if(cayendo)
            this.caer();
    }
    /**
     * Verifica si es posible que se active
     * la opcion de Brincar, si es asi se llama al metodo 
     * que lo activa
     */
    public void activaBrinco()
    {
        if(!cayendo && !brincando) //&& (piso!=null || esca!=null) )
        {
            i=0;
            if((piso!=null && piso.monoBrincando(dir)) || esca!=null)
                this.accionActiva(V,F,F,F,F);
            salto.play();
        }
    }
    
    /**
     * Verifica si es posible que se active
     * la opcion de Caminar, si es asi se llama al metodo 
     * que lo activa
     */
    public void activaCaminar()
    {
        if(!brincando && !cayendo)// && !subiendo
            this.accionActiva(F,V,F,F,F);
    }
    /**
     * Verifica si es posible que se active
     * la opcion de Frenar, si es asi se llama al metodo 
     * que lo activa
     */
    public void activaFrenar()
    {
        if(!brincando && !caminando && !subiendo && !cayendo )//!resbalando
            this.accionActiva(F,F,V,F,F);
    }
    /**
     * Verifica si es posible que se active
     * la opcion de subir, si es asi se llama al metodo 
     * que lo activa
     */
    public void activaSubir()
    {
        if(esca != null)
        {
            if(subiendo)
                i=i<SUBE-1?i+1:1;
            else
                i=0;
            this.accionActiva(F,F,F,V,F);
        }
    }
    /**
     * Verifica si es posible que se active
     * la opcion de Caer, si es asi se llama al metodo 
     * que lo activa
     */
    public void activaCaer()
    {
        if(!brincando && !frenando && !subiendo && piso == null)// && !resbalando  && !caminando)
        {
            this.accionActiva(F,F,F,F,V);
        }
    }
    /**
     * Hace el cambio de las imagenes que dan el efecto de movimiento del
     * Mono durante el brinco asi como de la nueva localizacion de este
     */
    public void brincar()
    {
        if(i < BRINCA){
            if(dir == DER && this.puedeAvanzar(10,0) && this.puedeAvanzar(0,-15)){
                this.setImage(imagenesbd[i]);
                this.setLocation((x<getWorld().getWidth()-10)?x+6:x,y+(i<BRINCA/2?-6:+6));    
            }
            if(dir == IZQ && this.puedeAvanzar(-10,0) && this.puedeAvanzar(0,-15)){
                this.setImage(imagenesbi[i]);
                this.setLocation((x>10)?x-6:x,y+(i<BRINCA/2?-6:+6));        
            }
            if(dir == NODIR && this.puedeAvanzar(0,-20)){
                this.setImage(imagenesba[i]);
                this.setLocation(x,(i<3)?y:y-6);   
            }
            if(dir == ABA && i==0 && (piso instanceof PastoCorto || piso instanceof PastoLargo))
                this.setLocation(x,y+15);
            i=(dir==ARR||dir==ABA)?BRINCA:i+1;
            
        }
        if(i==BRINCA){  
            i=1;
            brincando = false;
                    
        }
        
    }
    /**
     * Hace el cambio de las imagenes que dan el efecto de movimiento del
     * Mono cuando Camina asi como de la nueva localizacion de este
     */
    public void caminar()
    {
        if(dir == DER){
            this.setImage(imagenesd[i<CAMINA?i++:(i=8)]);
            if(i>3 && x<getWorld().getWidth()-10 && this.puedeAvanzar(3,0)){
                this.move(3);     
                if(piso != null)
                    piso.monoCaminando(dir);
            }
                
        }
        if(dir == IZQ){
            this.setImage(imagenesi[i<CAMINA?i++:(i=8)]);
            if(i>3 && x>10 && this.puedeAvanzar(-3,0)){
                this.move(-3);
                if(piso != null)
                    piso.monoCaminando(dir);
            }
        }
        
        caminando = false;
    }
    /**
     * Hace el cambio de las imagenes que dan el efecto de movimiento del
     * Mono Cuando sube una escalera asi como de la nueva localizacion de este
     */
    public void subir()
    {
        this.setImage(imagenessub[i]);
        if(dir == ARR && this.puedeAvanzar(0,-20))
            this.setLocation(x,y-2);
        if(dir == ABA)
            this.setLocation(x,y+2);
        dir = NODIR;
    }
    /**
     * Hace el cambio de las imagenes despues de soltar el boton right o left
     * para regresar a la posicion en la que el Mono se encuentra detenido
     */
    public void frenar()
    {  
        if(i<CAMINA-1 && i>8)
            i++;
        else
            i=(i==CAMINA-1?8:i-1);
        if(dir == DER){
            this.setImage(imagenesd[i]);
            if(i>3 && x<getWorld().getWidth()-10 && this.puedeAvanzar(0,0)){
                this.move(3);        
                if(piso != null)
                    piso.monoCaminando(dir);
            }
        }
        if(dir == IZQ){
            this.setImage(imagenesi[i]);
            if(i>3 && x>10 && this.puedeAvanzar(0,0)){
                this.move(-3);
                if(piso != null)
                    piso.monoCaminando(dir);
            }
        }
        if(i == 0){
            this.setImage(imagenesi[i]);
            dir = NODIR;
            frenando = false;
        }
    }
    /**
     * Hace el cambio de las imagenes que dan el efecto de movimiento del
     * Mono durante la caida asi como de la nueva localizacion de este
     */
    public void caer()
    {
        if(this.y>598)
        {
            ((Escenario)this.getWorld()).poneCeroCrono();
        }
        this.setImage(imagenescae[i=(i==0)?1:(i<CAE-1?i+1:1)]);
        if(dir == DER)
            this.setLocation(x+3,y+10);
        if(dir == IZQ)
            this.setLocation(x-3,y+10);
        if(dir != DER && dir != IZQ)
            this.setLocation(x,y+10);
        dir = NODIR;
        
    }
    /**
     * Asigna el piso y la escalera colindantes al
     * Mono en cierta posicion
     */
    public void asignaEscenario()
    {
        piso = (Piso)this.getOneObjectAtOffset(0,18,Piso.class);
        esca = (Escalera)this.getOneObjectAtOffset(0,0,Escalera.class);
    }
    /**
     * Verifica si el Mono no se topo con alguna pared que le
     * evite avanzar, puede atravesar el pasto
     * @param cx coordenada en x a verificar
     * @param cy coordenada en y a verificar
     * @return true en caso de poder avanzar
     * @return false en caso contrario
     */
    public boolean puedeAvanzar(int cx,int cy)
    {
        Piso p = (Piso)this.getOneObjectAtOffset(cx,cy,Piso.class);
        if(p == null || p instanceof PastoCorto || p instanceof PastoLargo)
            return true;
        else
        {
            if(brincando)
                i=BRINCA-1;
            return false;
        }
    }

    /**
     * Activa alguna accion del mono
     * @param bri si se encuentra brincando
     * @param cam si se encuentra caminando
     * @param fre si se encuentra frenando
     * @param sub si se encuentra subiendo
     * @param cay si se encuentra cayendo
     */   
    public void accionActiva(boolean bri,boolean cam,boolean fre,boolean sub,boolean cay)
    {
        brincando = bri;                  
        caminando = cam;
        frenando = fre;
        subiendo = sub;          
        cayendo = cay;                     
    }

    /**
     * Resta una vidas al mono y checa si pierde, en caso de perder envia un mensaje y nos regresa al menu principal
     */
    public void quitaVida()
    {
        if (vidas>0)
        {
            vidas--;
            muere.play();
            this.setLocation(20,570);
        }
        else
        {
            ((Escenario)this.getWorld()).remueveLamp();
            ((Escenario)this.getWorld()).addObject(pierde,400,300);
            vidas=3;
            Greenfoot.delay(100);
            ((Escenario)this.getWorld()).cambiaEscenario(4);
        }
    }
    
    /**
     * Nos da el valor entero de las vidas del mono.
     * @return Regresa el entero con las vidas.
     */
    public int regresaVidas()
    {
        return vidas;   
    }
    
    /**
     * Checa si el mono intersecta con algun objeto de tipo Obstaculo para llamar a su metodo accion.
     */
    public void checaObstaculo()
    {
        obs = (Obstaculo)this.getOneIntersectingObject(Obstaculo.class);
        if(obs != null && obs instanceof Obstaculo)// && obj instanceof Obstaculo)
             obs.accion();
    }
    
    /**
     * Actualiza la imagen del mono
     * @param v Es el valor que tomara el indice de las imagenes del mono
     */
    public void actualizaI(int v)
    {
        i = v;
    }
}
